package com.company.nuwa.mybatis.generator.plugins;

import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.*;
import org.mybatis.generator.codegen.mybatis3.MyBatis3FormattingUtilities;

import java.util.List;

/**
 * <p>example扩展插件</p>
 *
 * @author: Spector
 * @version: 1.0
 * @date 2023/12/12 18:42
 */
public class ExampleExtensionPlugin extends PluginAdapter {

    @Override
    public boolean validate(List<String> list) {
        return true;
    }

    @Override
    public boolean modelExampleClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        topLevelClass.addImportedType("org.apache.commons.lang3.StringUtils");
        topLevelClass.addImportedType("java.util.Objects");
        topLevelClass.getInnerClasses().stream()
                .filter(this::isGeneratedCriteria)
                .findFirst()
                .ifPresent(c -> addMethods(introspectedTable, c));

        return true;
    }

    private boolean isGeneratedCriteria(InnerClass innerClass) {
        return "GeneratedCriteria".equals(innerClass.getType().getShortName());
    }

    /**
     * 添加方法
     *
     * @param introspectedTable 内省表
     * @param criteria          标准
     */
    private void addMethods(IntrospectedTable introspectedTable, InnerClass criteria) {
        introspectedTable.getNonBLOBColumns().stream()
                .map(introspectedColumn -> addIfPresentMethod("EqualTo", "=", false, introspectedColumn))
                .forEach(criteria::addMethod);

        introspectedTable.getNonBLOBColumns().stream()
                .map(introspectedColumn -> addIfPresentMethod("NotEqualTo", "<>", false, introspectedColumn))
                .forEach(criteria::addMethod);

        introspectedTable.getNonBLOBColumns().stream()
                .map(introspectedColumn -> addIfPresentMethod("GreaterThan", ">", false, introspectedColumn))
                .forEach(criteria::addMethod);

        introspectedTable.getNonBLOBColumns().stream()
                .map(introspectedColumn -> addIfPresentMethod("GreaterThanOrEqualTo", ">=", false, introspectedColumn))
                .forEach(criteria::addMethod);

        introspectedTable.getNonBLOBColumns().stream()
                .map(introspectedColumn -> addIfPresentMethod("LessThan", "<", false, introspectedColumn))
                .forEach(criteria::addMethod);

        introspectedTable.getNonBLOBColumns().stream()
                .map(introspectedColumn -> addIfPresentMethod("LessThanOrEqualTo", "<=", false, introspectedColumn))
                .forEach(criteria::addMethod);

        // between
        introspectedTable.getNonBLOBColumns().stream()
                .map(introspectedColumn -> addIfPresentMethod("Between", "", true, introspectedColumn))
                .forEach(criteria::addMethod);
        introspectedTable.getNonBLOBColumns().stream()
                .map(introspectedColumn -> addIfPresentMethod("NotBetween", "not", true, introspectedColumn))
                .forEach(criteria::addMethod);

        // like语句
        introspectedTable.getNonBLOBColumns().stream()
                .filter(IntrospectedColumn::isStringColumn)
                .map(introspectedColumn -> addIfPresentMethod("Like", "like", false, introspectedColumn))
                .forEach(criteria::addMethod);

        introspectedTable.getNonBLOBColumns().stream()
                .filter(IntrospectedColumn::isStringColumn)
                .map(introspectedColumn -> addIfPresentMethod("NotLike", "not like", false, introspectedColumn))
                .forEach(criteria::addMethod);
    }

    /**
     * 添加if present方法
     *
     * @param prefix             方法名前缀
     * @param symbol             符号
     * @param isBetween          是否是between
     * @param introspectedColumn 内部列
     * @return {@link Method}
     */
    private Method addIfPresentMethod(String prefix, String symbol, Boolean isBetween, final IntrospectedColumn introspectedColumn) {
        StringBuilder sb = new StringBuilder();
        sb.append(introspectedColumn.getJavaProperty());
        sb.setCharAt(0, Character.toUpperCase(sb.charAt(0)));
        sb.insert(0, "and");
        sb.append(prefix);
        sb.append("IfPresent");
        Method method = new Method(sb.toString());
        method.setVisibility(JavaVisibility.PUBLIC);

        method.setReturnType(FullyQualifiedJavaType.getCriteriaInstance());

        // 通用的方法处理
        if (!isBetween) {
            method.addParameter(new Parameter(introspectedColumn
                    .getFullyQualifiedJavaType(), "value"));
            buildCommonIfExpression(introspectedColumn, sb);
            method.addBodyLine(sb.toString());

            buildCommonMethodBody(symbol, introspectedColumn, sb);
            method.addBodyLine(sb.toString());
        } else {
            // 添加between方法
            method.addParameter(new Parameter(introspectedColumn
                    .getFullyQualifiedJavaType(), "value1"));
            method.addParameter(new Parameter(introspectedColumn
                    .getFullyQualifiedJavaType(), "value2"));
            buildBetweenMethodBody(introspectedColumn, symbol, sb, method);
            // method.addBodyLine(sb.toString());
        }

        sb.setLength(0);
        sb.append("}");
        method.addBodyLine(sb.toString());
        method.addBodyLine("return (Criteria) this;");
        return method;
    }

    /**
     * 生成通用的if表达式
     *
     * @param introspectedColumn 内部列
     * @param sb                 某人
     */
    private void buildCommonIfExpression(IntrospectedColumn introspectedColumn, StringBuilder sb) {
        sb.setLength(0);
        String ifExpression = buildIfCondition(introspectedColumn.getFullyQualifiedJavaType().getShortNameWithoutTypeArguments());
        sb.append(ifExpression);
    }

    /**
     * 构建通用ifPresent方法体
     *
     * @param symbol             象征
     * @param introspectedColumn 内部列
     * @param sb                 某人
     */
    private static void buildCommonMethodBody(String symbol, IntrospectedColumn introspectedColumn, StringBuilder sb) {
        sb.setLength(0);
        sb.append("addCriterion(\"");
        sb.append(MyBatis3FormattingUtilities.getAliasedActualColumnName(introspectedColumn));
        sb.append(" ").append(symbol).append(" \", value, \"");
        sb.append(introspectedColumn.getJavaProperty());
        sb.append("\");");
    }

    /**
     * 构建between ifPresent方法体
     *
     * @param introspectedColumn 内部列
     * @param symbol             符号
     * @param sb                 方法体构建字符串
     * @param method             方法
     */
    private void buildBetweenMethodBody(IntrospectedColumn introspectedColumn, String symbol, StringBuilder sb, Method method) {
        var isNot = "not".equals(symbol);

        sb.setLength(0);
        String paramType = introspectedColumn.getFullyQualifiedJavaType().getShortNameWithoutTypeArguments();
        if ("String".equals(paramType)) {
            sb.append("if (StringUtils.isNotBlank(value1) && StringUtils.isNotBlank(value2)) {");
            method.addBodyLine(sb.toString());
            sb.setLength(0);
            sb.append("addCriterion(\"");
            sb.append(MyBatis3FormattingUtilities.getAliasedActualColumnName(introspectedColumn));
            sb.append(isNot ? " not between" : " between").append(" \", value1, value2, \"");
            sb.append(introspectedColumn.getJavaProperty());
            sb.append("\");");
            method.addBodyLine(sb.toString());
            method.addBodyLine("return (Criteria) this;");
            sb.setLength(0);
            sb.append("}");
            method.addBodyLine(sb.toString());
            sb.setLength(0);

            sb.append("if (StringUtils.isNotBlank(value1)) {");
            method.addBodyLine(sb.toString());
            sb.setLength(0);
            sb.append("addCriterion(\"");
            sb.append(MyBatis3FormattingUtilities.getAliasedActualColumnName(introspectedColumn));
            sb.append(isNot ? " <=" : " >=").append(" \", value1, \"");
            sb.append(introspectedColumn.getJavaProperty());
            sb.append("\");");
            method.addBodyLine(sb.toString());
            method.addBodyLine("return (Criteria) this;");
            sb.setLength(0);
            sb.append("}");
            method.addBodyLine(sb.toString());
            sb.setLength(0);

            sb.append("if (StringUtils.isNotBlank(value2)) {");
            method.addBodyLine(sb.toString());
            sb.setLength(0);
            sb.append("addCriterion(\"");
            sb.append(MyBatis3FormattingUtilities.getAliasedActualColumnName(introspectedColumn));
            sb.append(isNot ? " >=" : " <=").append(" \", value2, \"");
            sb.append(introspectedColumn.getJavaProperty());
            sb.append("\");");
            method.addBodyLine(sb.toString());
            sb.setLength(0);
            method.addBodyLine("return (Criteria) this;");
            return;
        }

        sb.append("if (Objects.nonNull(value1) && Objects.nonNull(value2)) {");
        method.addBodyLine(sb.toString());
        sb.setLength(0);
        sb.append("addCriterion(\"");
        sb.append(MyBatis3FormattingUtilities.getAliasedActualColumnName(introspectedColumn));
        sb.append(isNot ? " not between" : " between").append(" \", value1, value2, \"");
        sb.append(introspectedColumn.getJavaProperty());
        sb.append("\");");
        method.addBodyLine(sb.toString());
        method.addBodyLine("return (Criteria) this;");
        sb.setLength(0);
        sb.append("}");
        method.addBodyLine(sb.toString());
        sb.setLength(0);

        sb.append("if (Objects.nonNull(value1)) {");
        method.addBodyLine(sb.toString());
        sb.setLength(0);
        sb.append("addCriterion(\"");
        sb.append(MyBatis3FormattingUtilities.getAliasedActualColumnName(introspectedColumn));
        sb.append(isNot ? " <=" : " >=").append(" \", value1, \"");
        sb.append(introspectedColumn.getJavaProperty());
        sb.append("\");");
        method.addBodyLine(sb.toString());
        method.addBodyLine("return (Criteria) this;");
        sb.setLength(0);
        sb.append("}");
        method.addBodyLine(sb.toString());
        sb.setLength(0);

        sb.append("if (Objects.nonNull(value2)) {");
        method.addBodyLine(sb.toString());
        sb.setLength(0);
        sb.append("addCriterion(\"");
        sb.append(MyBatis3FormattingUtilities.getAliasedActualColumnName(introspectedColumn));
        sb.append(isNot ? " >=" : " <=").append(" \", value2, \"");
        sb.append(introspectedColumn.getJavaProperty());
        sb.append("\");");
        method.addBodyLine(sb.toString());
        sb.setLength(0);
        method.addBodyLine("return (Criteria) this;");
    }

    /**
     * 生成if表达式语句
     *
     * @param paramType 参数类型
     * @return {@link String}
     */
    private String buildIfCondition(String paramType) {
        if ("String".equals(paramType)) {
            return "if (StringUtils.isNotBlank(value)) {";
        }

        return "if (Objects.nonNull(value)) {";
    }

}
